import json
from datetime import datetime

from flask import jsonify, current_app, request, session
from app import redis, constants
from app.api_v1_0 import api
from app import models, db
from app.utils.image_storage import storage
from app.utils.response_code import RET
from app.utils.commons import login_required

import json
from flask import jsonify, current_app
from app import redis, constants
from app.api_v1_0 import api
from app.models import User
from app.utils.response_code import RET


@api.route('/areas', methods=['GET'])
def get_areas_info():
    """
    获取城区信息
    :return:
    """
    try:
        resp_json = redis.get("area_info")
    except Exception as e:
        current_app.logger.error(e)
    else:
        if resp_json is not None:
            # 如果查询出来不为None则表示redis有缓存，那么直接使用缓存数据返回到前端使用
            return resp_json, 200, {'Content-Type': 'application/json'}


    try:
        area_list = Area.query.all()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="数据库查询异常")
    # 转换成字典
    area_dict_list = []
    for area in area_list:
        # [{"aid": self.id, "aname": self.name}, {"aid": self.id, "aname": self.name}]
        area_dict_list.append(area.to_dict())

    # 转换成字典，
    resp_dict = dict(errno=RET.OK, errmsg="OK", data=area_dict_list)
    resp_json = json.dumps(resp_dict)

    # 设置过期时间
    try:
        redis.setex("area_info", constants.AREA_INFO_REDIS_EXPIRES, resp_json)
    except Exception as e:
        current_app.logger.error(e)

    return resp_json, 200, {'ContentType': 'application/json'}


@api.route("/user/houses", methods=["GET"])
@login_required
def get_user_houses():
    """获取房东发布的房源信息条目"""
    # 从g对象获取用户id
    user_id = session.get("user_id")

    try:
        # 根据user_id查询用户信息，通过用户信息关联查询其发布的房源信息
        user = db.session.query(models.User).get(user_id)
        houses = user.houses
    except Exception as e:
        # 如果查询过程出现异常，记录日志并返回获取数据失败的响应
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="获取数据失败")

    # 将查询到的房屋信息转换为字典存放到列表中
    houses_list = []
    if houses:
        for house in houses:
            # 将房源信息转换为字典，并添加到houses_list列表中
            houses_list.append(house.to_basic_dict())

    # 返回响应，包含房源信息列表
    return jsonify(errno=RET.OK, errmsg="OK", data={"houses": houses_list})


@api.route('/houses/info', methods=['POST'])
@login_required
def save_house_info():
    """保存房屋的基本信息，（这里不保存图片）
    前端发送过来的json数据
    {
        "title":"",
        "price":"",
        "area_id":"1",
        "address":"",
        "room_count":"",
        "acreage":"",
        "unit":"",
        "capacity":"",
        "beds":"",
        "deposit":"",
        "min_days":"",
        "max_days":"",
        "facility":["7","8"]  房屋设施信息，需要单独设置
    }
    """
    # 从g对象获取用户id
    user_id = session.get("user_id")

    # 获取前端传递过来的数据
    house_data = request.get_json()

    # 提取房屋信息各字段
    title = house_data.get("title")  # 房屋名称标题
    price = house_data.get("price")  # 房屋单价
    area_id = house_data.get("area_id")  # 房屋所属城区的编号
    address = house_data.get("address")  # 房屋地址
    room_count = house_data.get("room_count")  # 房屋包含的房间数目
    acreage = house_data.get("acreage")  # 房屋面积
    unit = house_data.get("unit")  # 房屋布局（几室几厅)
    capacity = house_data.get("capacity")  # 房屋容纳人数
    beds = house_data.get("beds")  # 房屋卧床数目
    deposit = house_data.get("deposit")  # 押金
    min_days = house_data.get("min_days")  # 最小入住天数
    max_days = house_data.get("max_days")  # 最大入住天数

    # 校验参数完整性，确保所有必要字段都存在
    if not all(
            [title, price, area_id, address, room_count, acreage, unit, capacity, beds, deposit, min_days, max_days]):
        return jsonify(errno=RET.PARAMERR, errmsg="参数缺失")

    # 判断前端传递过来的金额格式是否正确, 默认是单位是：分，所以转换
    try:
        price = int(float(price) * 100)  # 转换房屋金额为整数，单位为分
        deposit = int(float(deposit) * 100)  # 转换押金为整数，单位为分
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")

    # 判断前端传递过来的城市id是否存在
    try:
        # 表示城市数据存在，传递过来的id是正确的
        area = db.session.query(models.Area).get(area_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.NODATA, errmsg="城区信息错误")

    # 构建房源信息对象
    house = models.House(
        user_id=user_id,
        area_id=area_id,
        title=title,
        price=price,
        address=address,
        room_count=room_count,
        acreage=acreage,
        unit=unit,
        capacity=capacity,
        beds=beds,
        deposit=deposit,
        min_days=min_days,
        max_days=max_days
    )

    # 获取前端传递过来的房屋设施id信息，保存到列表
    facility_ids = house_data.get("facility")

    # 表示没有房屋设施，不为空则表示有房屋设施
    if facility_ids:
        try:
            # Facility.id.in_(facility_ids) in_在Facility数据库中查询出来facility_ids列表中的设施id对应的信息
            facilities = models.Facility.query.filter(models.Facility.id.in_(facility_ids)).all()
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg="数据库异常")

        # 不为空，则需要保存设施数据
        if facilities:
            house.facilities = facilities

    try:
        db.session.add(house)
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg="保存数据失败")

    # 保存数据, 响应中返回house.id
    return jsonify(errno=RET.OK, errmsg="ok", data={"house_id": house.id})


@api.route("/houses/image", methods=["POST"])
@login_required
def save_house_image():
    """保存房屋的图片
    参数 图片 房屋的id
    """
    # 获取前端上传的图片文件和房屋id
    image_file = request.files.get("house_image")
    house_id = request.form.get("house_id")

    # 校验参数完整性
    if not all([image_file, house_id]):
        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")

    # 判断house_id正确性
    try:
        house = db.session.query(models.House).get(house_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="数据库异常")

    # 如果house对象不存在
    if house is None:
        return jsonify(errno=RET.NODATA, errmsg="房屋不存在")

    # 读取图片数据
    image_data = image_file.read()
    # 保存图片到七牛云存储中
    try:
        file_name = storage(image_data)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.THIRDERR, errmsg="保存图片失败")

    # 保存图片信息到数据库中
    house_image = HouseImage(house_id=house_id, url=file_name)
    db.session.add(house_image)

    # 处理房屋的主图片，如果主图片不存在，则设置当前图片为主图片
    if not house.index_image_url:
        house.index_image_url = file_name
        db.session.add(house)

    # 提交数据库事务
    try:
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg="保存图片数据异常")

    # 拼接图片完整URL
    image_url = constants.QINIU_DOMIN_PREFIX + file_name

    # 返回成功信息和图片URL
    return jsonify(errno=RET.OK, errmsg="OK", data={"image_url": image_url})


@api.route("/houses/<int:house_id>", methods=["GET"])
def get_house_detail(house_id):
    """获取房屋详情"""
    # 前端在房屋详情页面展示时，如果浏览页面的用户不是该房屋的房东，则展示预定按钮，否则不展示，
    # 所以需要后端返回登录用户的user_id
    # 尝试获取用户登录的信息，若登录，则返回给前端登录用户的user_id，否则返回user_id=-1
    user_id = session.get("user_id", "-1")  # 获取当前用户的ID，如果用户未登录，则设置为-1

    # 校验参数
    if not house_id:
        return jsonify(errno=RET.PARAMERR, errmsg="参数缺失")  # 检查 house_id 参数是否存在

    # 先从redis缓存中获取信息
    try:
        ret = redis.get(f"house_info_{house_id}")  # 尝试从 Redis 缓存中获取房屋信息
    except Exception as e:
        current_app.logger.error(e)
        ret = None

    if ret:
        current_app.logger.info("hit house info redis")  # 记录缓存命中的日志
        house_data = json.loads(ret)  # 将缓存中的 JSON 字符串转换为字典
        return jsonify(errno=RET.OK, errmsg="OK", data={"user_id": user_id, "house": house_data})  # 返回缓存的数据

    # 查询数据库
    try:
        house = db.session.query(models.House).get(house_id)  # 从数据库中查询房屋信息
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="查询数据失败")  # 如果查询失败，返回数据库错误

    if not house:
        return jsonify(errno=RET.NODATA, errmsg="房屋不存在")  # 如果未查询到房屋，返回无数据错误

    # 将房屋对象数据转换为字典
    try:
        house_data = house.to_full_dict()  # 将房屋对象的数据转换为字典格式
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg="数据出错")  # 如果转换失败，返回数据错误

    # 存入到redis中
    json_house = json.dumps(house_data)  # 将字典转换为 JSON 字符串
    try:
        redis.setex(f"house_info_{house_id}", constants.HOUSE_DETAIL_REDIS_EXPIRE_SECOND, json_house)  # 将数据缓存到 Redis
    except Exception as e:
        current_app.logger.error(e)  # 记录缓存错误日志

    return jsonify(errno=RET.OK, errmsg="OK", data={"user_id": user_id, "house": house_data})  # 返回房屋详情数据

@api.route("/houses/index", methods=["GET"])
def get_house_index():
    """获取主页幻灯片展示的房屋基本信息"""
    # 从缓存中尝试获取数据
    try:
        ret = redis.get("home_page_data")
    except Exception as e:
        # 记录获取缓存数据时的异常
        current_app.logger.error(e)
        ret = None

    if ret:
        # 缓存命中，记录日志信息
        current_app.logger.info("hit house index info redis")
        # 因为redis中保存的是json字符串，所以直接返回解码后的JSON数据
        return jsonify(errno=RET.OK, errmsg="OK", data=json.loads(ret))
    else:
        try:
            # 查询数据库，返回房屋订单数目最多的5条数据
            houses = db.session.query(models.House).order_by(models.House.order_count.desc()).limit(constants.HOME_PAGE_MAX_HOUSES)
        except Exception as e:
            # 记录查询数据库时的异常
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg="查询数据失败")

        if not houses:
            # 如果没有查询到数据，返回无数据的响应
            return jsonify(errno=RET.NODATA, errmsg="查询无数据")

        houses_list = []

        for house in houses:
            # 如果房屋未设置主图片，则跳过该房屋
            if not house.index_image_url:
                continue
            # 将房屋信息转换为字典并添加到列表中
            houses_list.append(house.to_basic_dict())

        # 将数据转换为json字符串，并保存到redis缓存
        json_houses = json.dumps(houses_list)  # "[{},{},{}]"
        try:
            # 设置缓存数据，包含过期时间
            redis.setex("home_page_data", constants.HOME_PAGE_DATA_REDIS_EXPIRES, json_houses)
        except Exception as e:
            # 记录设置缓存数据时的异常
            current_app.logger.error(e)

        # 返回查询到的房屋信息
        return jsonify(errno=RET.OK, errmsg="OK", data=houses_list)


@api.route("/houses")
def get_house_list():
    """获取房屋的列表信息（搜索页面）"""

    # 获取请求参数
    start_date = request.args.get("sd", "")  # 用户想要的起始时间
    end_date = request.args.get("ed", "")  # 用户想要的结束时间
    area_id = request.args.get("aid", "")  # 区域编号
    sort_key = request.args.get("sk", "new")  # 排序关键字，默认为"new"
    page = request.args.get("p")  # 页数

    # 处理时间参数，确保格式正确并且起始时间不晚于结束时间
    try:
        if start_date:
            # 将字符串形式的日期转换为datetime对象
            start_date = datetime.strptime(start_date, "%Y-%m-%d")

        if end_date:
            # 将字符串形式的日期转换为datetime对象
            end_date = datetime.strptime(end_date, "%Y-%m-%d")

        if start_date and end_date:
            # 确保起始日期不晚于结束日期
            assert start_date <= end_date
    except Exception as e:
        # 如果日期转换或验证出错，记录错误日志并返回参数错误响应
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg="日期参数有误")

    # 判断区域id是否有效
    if area_id:
        try:
            # 检查该区域ID是否在数据库中存在
            area = Area.query.get(area_id)
        except Exception as e:
            # 如果区域ID查询出错，记录错误日志并返回参数错误响应
            current_app.logger.error(e)
            return jsonify(errno=RET.PARAMERR, errmsg="区域参数有误")

    # 处理页数参数，确保是整数
    try:
        page = int(page)
    except Exception as e:
        # 如果页数参数无效，则默认设置为1
        current_app.logger.error(e)
        page = 1

    # 从缓存中获取数据
    redis_key = "house_%s_%s_%s_%s" % (start_date, end_date, area_id, sort_key)
    try:
        # 尝试从Redis缓存中获取当前页的房屋列表数据
        resp_json = redis.hget(redis_key, page)
    except Exception as e:
        # 如果从Redis获取数据出错，记录错误日志
        current_app.logger.error(e)
    else:
        if resp_json:
            # 如果缓存中有数据，则直接返回
            return resp_json, 200, {"Content-Type": "application/json"}

    # 如果缓存中没有数据，则需要从数据库中查询
    filter_params = []  # 过滤条件的参数列表容器

    # 填充过滤参数
    # 根据时间查询冲突的订单
    conflict_orders = None
    try:
        if start_date and end_date:
            # 查询冲突的订单，即在指定时间段内已经被预定的订单
            conflict_orders = db.session.query(models.Order).filter(models.Order.begin_date <= end_date, models.Order.end_date >= start_date).all()
        elif start_date:
            # 查询在指定起始日期之后的订单
            conflict_orders = db.session.query(models.Order).query.filter(models.Order.end_date >= start_date).all()
        elif end_date:
            # 查询在指定结束日期之前的订单
            conflict_orders = db.session.query(models.Order).filter(models.Order.begin_date <= end_date).all()
    except Exception as e:
        # 如果查询订单出错，记录错误日志并返回数据库错误响应
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="数据库异常")

    if conflict_orders:
        # 如果存在冲突的订单，获取这些订单涉及的房屋ID
        conflict_house_ids = [order.house_id for order in conflict_orders]

        if conflict_house_ids:
            # 将冲突房屋ID添加为过滤条件
            filter_params.append(models.House.id.notin_(conflict_house_ids))

    # 根据区域ID添加过滤条件
    if area_id:
        filter_params.append(models.House.area_id == area_id)

    # 根据排序关键字添加排序条件并查询数据库
    if sort_key == "booking":  # 按照订单数排序
        house_query = models.House.query.filter(*filter_params).order_by(models.House.order_count.desc())
    elif sort_key == "price-inc":  # 按照价格升序排序
        house_query = models.House.query.filter(*filter_params).order_by(models.House.price.asc())
    elif sort_key == "price-des":  # 按照价格降序排序
        house_query = models.House.query.filter(*filter_params).order_by(models.House.price.desc())
    else:  # 按照房屋创建时间排序，默认排序
        house_query = models.House.query.filter(*filter_params).order_by(models.House.create_time.desc())

    # 处理分页
    try:
        # 分页查询，获取当前页的房屋列表数据
        page_obj = house_query.paginate(page=page, per_page=constants.HOUSE_LIST_PAGE_CAPACITY, error_out=False)
    except Exception as e:
        # 记录错误日志并返回数据库错误响应
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="数据库异常")

    # 获取当前页的房屋对象列表
    house_li = page_obj.items
    houses = [house.to_basic_dict() for house in house_li]  # 将房屋对象转换为字典格式

    # 获取总页数
    total_page = page_obj.pages

    # 构造响应数据
    resp_dict = dict(errno=RET.OK, errmsg="OK", data={"total_page": total_page, "houses": houses, "current_page": page})
    resp_json = json.dumps(resp_dict)  # 将响应数据转换为JSON格式

    # 如果当前页数小于等于总页数，则缓存结果
    if page <= total_page:
        redis_key = f"house_{start_date}_{end_date}_{area_id}_{sort_key}"
        # 创建一个唯一的缓存键，用于存储和检索Redis中的缓存数据。使用字符串格式化操作将 start_date、end_date、area_id 和 sort_key 组合成一个字符串键。
        try:
            # 使用Redis管道来设置多个命令，保证原子性
            pipeline = redis.pipeline()
            pipeline.multi()
            pipeline.hset(redis_key, page, resp_json)
            pipeline.expire(redis_key, constants.HOUES_LIST_PAGE_REDIS_CACHE_EXPIRES)
            pipeline.execute()

        except Exception as e:
            current_app.logger.error(e)

    # 返回响应数据
    return resp_json, 200, {"Content-Type": "application/json"}
